package com.only4play.codegen.processor.service;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.auto.service.AutoService;
import com.google.common.base.CaseFormat;
import com.only4play.codegen.context.DefaultNameContext;
import com.only4play.codegen.processor.BaseCodeGenProcessor;
import com.only4play.codegen.processor.CodeGenProcessor;
import com.only4play.codegen.support.base.EntityOperations;
import com.only4play.codegen.support.constants.CodeEnum;
import com.only4play.codegen.support.exception.BusinessException;
import com.only4play.codegen.support.model.PageRequestWrapper;
import com.only4play.codegen.util.StringUtils;
import com.squareup.javapoet.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import java.lang.annotation.Annotation;
import java.util.Optional;
import java.util.stream.Collectors;

@AutoService(value = CodeGenProcessor.class)
public class GenServiceImplProcessor extends BaseCodeGenProcessor {

  public static final String IMPL_SUFFIX = "ServiceImpl";

  public GenServiceImplProcessor() {
  }

  @Override
  protected void generateClass(TypeElement typeElement, RoundEnvironment roundEnvironment) {
    DefaultNameContext nameContext = getNameContext(typeElement);
    String className = typeElement.getSimpleName() + IMPL_SUFFIX;
      ClassName mapperClassName = ClassName.get(nameContext.getRepositoryPackageName(), nameContext.getRepositoryClassName());
      ClassName serviceClassName = ClassName.get(nameContext.getServicePackageName(), nameContext.getServiceClassName());
    TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(className)
            .addSuperinterface(serviceClassName)
            .superclass(ParameterizedTypeName.get(
                    ClassName.get(ServiceImpl.class),
                    mapperClassName,
                    ClassName.get(typeElement)
                    )
            )
            .addAnnotation(Transactional.class)
            .addAnnotation(Service.class)
            .addAnnotation(Slf4j.class)
            .addAnnotation(RequiredArgsConstructor.class)
            .addJavadoc("@author mdy")
            .addModifiers(Modifier.PUBLIC);
    if(StringUtils.containsNull(nameContext.getRepositoryPackageName())){
      return;
    }
    String repositoryFieldName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,
            nameContext.getRepositoryClassName());
    String classFieldName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL,
            typeElement.getSimpleName().toString());
    FieldSpec repositoryField = FieldSpec
            .builder(ClassName.get(nameContext.getRepositoryPackageName(),
                    nameContext.getRepositoryClassName()), repositoryFieldName)
            .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
            .build();
    typeSpecBuilder.addField(repositoryField);
    Optional<MethodSpec> createMethod = createMethod(typeElement, nameContext, repositoryFieldName,
            classFieldName);
    createMethod.ifPresent(m -> typeSpecBuilder.addMethod(m));
    Optional<MethodSpec> updateMethod = updateMethod(typeElement, nameContext, repositoryFieldName);
    updateMethod.ifPresent(m -> typeSpecBuilder.addMethod(m));
    Optional<MethodSpec> validMethod = validMethod(typeElement, repositoryFieldName);
    validMethod.ifPresent(m -> typeSpecBuilder.addMethod(m));
    Optional<MethodSpec> invalidMethod = invalidMethod(typeElement, repositoryFieldName);
    invalidMethod.ifPresent(m -> typeSpecBuilder.addMethod(m));
    Optional<MethodSpec> findByIdMethod = findByIdMethod(typeElement, nameContext,
            repositoryFieldName, classFieldName);
    findByIdMethod.ifPresent(m -> typeSpecBuilder.addMethod(m));
    Optional<MethodSpec> findByPageMethod = findByPageMethod(typeElement, nameContext,
            repositoryFieldName);
    findByPageMethod.ifPresent(m -> typeSpecBuilder.addMethod(m));
    genJavaSourceFile(generatePackage(typeElement),
            typeElement.getAnnotation(GenServiceImpl.class).sourcePath(), typeSpecBuilder);
  }

  @Override
  public Class<? extends Annotation> getAnnotation() {
    return GenServiceImpl.class;
  }

  @Override
  public String generatePackage(TypeElement typeElement) {
    return typeElement.getAnnotation(GenServiceImpl.class).pkgName();
  }

  private Optional<MethodSpec> createMethod(TypeElement typeElement, DefaultNameContext nameContext,
                                            String repositoryFieldName, String classFieldName) {
    boolean containsNull = StringUtils.containsNull(nameContext.getCreatorPackageName(),nameContext.getObjectMapperPackageName());
    if (!containsNull) {
      return Optional.of(MethodSpec.methodBuilder("create" + typeElement.getSimpleName())
              .addParameter(
                      ClassName.get(nameContext.getCreatorPackageName(), nameContext.getCreatorClassName()),
                      "creator")
              .addModifiers(Modifier.PUBLIC)
              .addCode(
                      CodeBlock.of(
                              "$T<$T> $L = $T.doCreate($L)\n.create(() -> $T.INSTANCE.dtoToEntity(creator))\n"
//                                      + ".update(e -> e.init())\n"
                                      + ".update("+ typeElement.getSimpleName()+"::init)\n"
                                      + ".execute();\n",
                              Optional.class,
                              typeElement, classFieldName, EntityOperations.class, repositoryFieldName,
                              ClassName.get(nameContext.getObjectMapperPackageName(),
                                      nameContext.getObjectMapperClassName()))
              )
              .addCode(
                      CodeBlock.of("return $L.isPresent() ? $L.get().getId() : 0;", classFieldName,
                              classFieldName)
              )
              .addJavadoc("createImpl")
              .addAnnotation(Override.class)
              .returns(Long.class).build());
    }
    return Optional.empty();
  }

  private Optional<MethodSpec> updateMethod(TypeElement typeElement, DefaultNameContext nameContext,
                                            String repositoryFieldName) {
    boolean containsNull = StringUtils.containsNull(nameContext.getUpdaterPackageName());
    if (!containsNull) {
      return Optional.of(MethodSpec.methodBuilder("update" + typeElement.getSimpleName())
              .addParameter(
                      ClassName.get(nameContext.getUpdaterPackageName(), nameContext.getUpdaterClassName()),
                      "updater")
              .addModifiers(Modifier.PUBLIC)
              .addCode(
                      CodeBlock.of("$T.doUpdate($L)\n.loadById(updater.getId())\n"
//                                      + ".update(e -> updater.update$L(e))\n"
                                      + ".update(updater::update$L)\n"
                                      + ".execute();",
                              EntityOperations.class, repositoryFieldName, typeElement.getSimpleName())
              )
              .addJavadoc("update")
              .addAnnotation(Override.class)
              .build());
    }
    return Optional.empty();
  }

  private Optional<MethodSpec> validMethod(TypeElement typeElement, String repositoryFieldName) {
    return Optional.of(MethodSpec.methodBuilder("valid" + typeElement.getSimpleName())
            .addParameter(Long.class, "id")
            .addModifiers(Modifier.PUBLIC)
            .addCode(
                    CodeBlock.of("$T.doUpdate($L)\n.loadById(id)\n"
//                                    + ".update(e -> e.valid())\n"
                                    + ".update("+ typeElement.getSimpleName()+"::valid)\n"
                                    + ".execute();",
                            EntityOperations.class, repositoryFieldName)
            )
            .addJavadoc("valid")
            .addAnnotation(Override.class)
            .build());
  }

  private Optional<MethodSpec> invalidMethod(TypeElement typeElement, String repositoryFieldName) {
    return Optional.of(MethodSpec.methodBuilder("invalid" + typeElement.getSimpleName())
            .addParameter(Long.class, "id")
            .addModifiers(Modifier.PUBLIC)
            .addCode(
                    CodeBlock.of("$T.doUpdate($L)\n.loadById(id)\n"
//                                    + ".update(e -> e.invalid())\n"
                                    + ".update("+ typeElement.getSimpleName()+"::invalid)\n"

                                    + ".execute();",
                            EntityOperations.class, repositoryFieldName)
            )
            .addJavadoc("invalid")
            .addAnnotation(Override.class)
            .build());
  }
  private Optional<MethodSpec> findByIdMethod(TypeElement typeElement,
                                              DefaultNameContext nameContext, String repositoryFieldName, String classFieldName) {
    boolean containsNull = StringUtils.containsNull(nameContext.getVoPackageName());
    if (!containsNull) {
      // 构建返回类型
      ClassName returnType = ClassName.get(nameContext.getVoPackageName(), nameContext.getVoClassName());
      // 构建实体类型
      ClassName entityClass = ClassName.get(typeElement);
      return Optional.of(MethodSpec.methodBuilder("findById")
              .addAnnotation(Override.class)
              .addModifiers(Modifier.PUBLIC)
              .addParameter(Long.class, "id")
              .addJavadoc("findById")
              .addCode(
                  CodeBlock.of(
                          "$T entity = $L.selectById(id);\n", entityClass, repositoryFieldName)
              )
              .addCode(
                      CodeBlock.of("if (entity == null) throw new $T($T.NotFindError);\n",
                              BusinessException.class,
                              CodeEnum.class
                      )
              )
              .addCode(
                      CodeBlock.of("$T entityVo = new $T();\n"
                              + "$T.copyProperties(entity,entityVo);\n"+
                              "return entityVo;",
                              returnType,
                              returnType,
//                              ClassName.get(nameContext.getEntityMapperPackageName(),
//                                      nameContext.getEntityMapperClassName()),
                              BeanUtil.class
                      )
              )
              .returns(returnType)
              .build());
    }
    return Optional.empty();
  }


  private Optional<MethodSpec> findByPageMethod(TypeElement typeElement,
                                                DefaultNameContext nameContext, String repositoryFieldName) {
    boolean containsNull = StringUtils.containsNull(nameContext.getQueryPackageName(),
            nameContext.getVoPackageName());
    if (!containsNull) {
      return Optional.of(MethodSpec.methodBuilder("findByPage")
              .addParameter(ParameterizedTypeName.get(ClassName.get(PageRequestWrapper.class),
                              ClassName.get(nameContext.getQueryPackageName(), nameContext.getQueryClassName())),
                      "query")
              .addModifiers(Modifier.PUBLIC)
              .addCode(
                      CodeBlock.of("$T<$T> queryWrapper = new $T<>();\n",
                              QueryWrapper.class,
                              typeElement,
                              QueryWrapper.class)
              )
              .addCode(
                      CodeBlock.of("queryWrapper.orderByDesc(\"create_time\");\n")
              )
              .addCode(
                      CodeBlock.of(
                              "$T<$T> page = $L. \n" +
                              "selectPage($T.of(query.getPage(), query.getPageSize()), queryWrapper);\n",
                              Page.class,
                              ClassName.get(typeElement),
                              repositoryFieldName,
                              Page.class
                      )

              )
              .addCode(
                      CodeBlock.of("return \n" +
                              "new $T<$T>().setRecords(page.getRecords().stream().map(entity -> {\n" +
                              "   $T entityVo = new $T();\n" +
                              "   $T.copyProperties(entity,entityVo);\n"+
                              "   return entityVo;\n" +
                              "}).collect($T.toList()))\n" +
                              ".setTotal(page.getTotal())" +
                             ".setCurrent(page.getCurrent())" +
                              ".setSize(page.getSize())" +
                              ";",
                              PageDTO.class,
                              ClassName.get(nameContext.getVoPackageName(), nameContext.getVoClassName()),
//                              ClassName.get(nameContext.getEntityMapperPackageName(),
//                                      nameContext.getEntityMapperClassName()),
                              ClassName.get(nameContext.getVoPackageName(), nameContext.getVoClassName()),
                              ClassName.get(nameContext.getVoPackageName(), nameContext.getVoClassName()),
                              BeanUtil.class,
                              Collectors.class

                      )
              )
              .addJavadoc("findByPage")
              .addAnnotation(Override.class)
              .returns(ParameterizedTypeName.get(ClassName.get(Page.class),
                      ClassName.get(nameContext.getVoPackageName(), nameContext.getVoClassName())))
              .build());
    }
    return Optional.empty();
  }

}
